package toolbox

import (
	"encoding/base64"
	"encoding/binary"
	"errors"
	"fmt"
	"strconv"
	"sync"
	"time"
)

var (
	// Epoch 2010-01-01 00:00:00
	Epoch int64 = 1288834974657
	// WorkerIdBits 10
	WorkerIdBits uint8 = 10
	StepBits     uint8 = 12

	maxWorkerId    int64 = -1 ^ (-1 << WorkerIdBits)
	workerMask     int64 = maxWorkerId << StepBits
	stepMask       int64 = -1 ^ (-1 << StepBits)
	workerIdShift  uint8 = StepBits
	timestampShift uint8 = StepBits + WorkerIdBits
)

const encodeBase32Map = "ybndrfg8ejkmcpqxot1uwisza345h769"
const encodeBase58Map = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"

var decodeBase32Map [256]byte
var decodeBase58Map [256]byte

var InvalidBase32Error = errors.New("invalid base32")
var InvalidBase58Error = errors.New("invalid base58")

// JsonError 定义个json错误
type JsonError struct {
	original []byte
}

func init() {
	for _, item := range decodeBase32Map {
		decodeBase32Map[item] = 0xFF
	}
	for i, item := range encodeBase32Map {
		decodeBase32Map[item] = byte(i)
	}
	for _, item := range decodeBase58Map {
		decodeBase58Map[item] = 0xFF
	}
	for i, item := range encodeBase58Map {
		decodeBase58Map[item] = byte(i)
	}
}
func (e JsonError) Error() string {
	return fmt.Sprintf("invalid snowflake ID %q in json", string(e.original))
}

type WorkerInfo struct {
	looker      sync.Mutex
	epoch       time.Time
	time        int64
	workerId    int64
	step        int64
	workerMask  int64
	workerMax   int64
	stepMask    int64
	timeShift   uint8
	workerShift uint8
}

// ID 定义雪花ID类型
type ID int64

func NewWorkerInfo(workerId int64) (*WorkerInfo, error) {
	if WorkerIdBits+StepBits > 22 {
		return nil, errors.New(fmt.Sprintf("invalid WorkerIdBits %d and StepBits %d", WorkerIdBits, StepBits))
	}
	maxWorkerId = -1 ^ (-1 << WorkerIdBits)
	workerMask = maxWorkerId << StepBits
	stepMask = -1 ^ (-1 << StepBits)
	timestampShift = StepBits + WorkerIdBits
	workerIdShift = StepBits

	workerInfo := &WorkerInfo{}
	workerInfo.workerId = workerId
	workerInfo.workerMax = -1 ^ (-1 << WorkerIdBits)
	workerInfo.stepMask = -1 ^ (-1 << StepBits)
	workerInfo.workerMask = workerInfo.workerMax << StepBits
	workerInfo.timeShift = StepBits + WorkerIdBits
	workerInfo.workerShift = StepBits

	if workerInfo.workerId < 0 || workerInfo.workerId > workerInfo.workerMax {
		return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(workerInfo.workerMax, 10))
	}

	var currentTime = time.Now()
	workerInfo.epoch = currentTime.Add(time.Unix(Epoch/1000, (Epoch%1000)*1000000).Sub(currentTime))
	return workerInfo, nil
}

func (w *WorkerInfo) Generate() ID {
	w.looker.Lock()
	defer w.looker.Unlock()
	currentTime := time.Since(w.epoch).Milliseconds()
	if currentTime == w.time {
		w.step = (w.step + 1) & w.stepMask
		if w.step == 0 {
			for currentTime <= w.time {
				currentTime = time.Since(w.epoch).Milliseconds()
			}
		}
	} else {
		w.step = 0
	}
	w.time = currentTime
	r := ID((currentTime << timestampShift) | (w.workerId << workerIdShift) | w.step)
	return r
}

// Int64
// 返回int64
func (f ID) Int64() int64 {
	return int64(f)
}

// String
// 返回字符串
func (f ID) String() string {
	return strconv.FormatInt(int64(f), 10)
}

// Base2 返回二进制字符串
func (f ID) Base2() string {
	return strconv.FormatInt(int64(f), 2)
}

func (f ID) Bytes() []byte {
	return []byte(f.String())
}

func (f ID) Base32() string {
	id := f
	if id < 32 {
		return string(encodeBase32Map[id])
	}
	b := make([]byte, 0, 12)
	for id >= 32 {
		b = append(b, encodeBase32Map[id%32])
		id /= 32
	}
	b = append(b, encodeBase32Map[id])
	for x, y := 0, len(b)-1; x < y; x, y = x+1, y-1 {
		b[x], b[y] = b[y], b[x]
	}
	return string(b)
}

func (f ID) Base58() string {
	id := f
	if id < 58 {
		return string(encodeBase58Map[id])
	}
	b := make([]byte, 0, 11)
	for id >= 58 {
		b = append(b, encodeBase58Map[id%58])
		id /= 58
	}
	b = append(b, encodeBase58Map[id])

	for x, y := 0, len(b)-1; x < y; x, y = x+1, y-1 {
		b[x], b[y] = b[y], b[x]
	}
	return string(b)
}

func (f ID) Base36() string {
	return strconv.FormatInt(int64(f), 36)
}

func (f ID) Base64() string {
	return base64.StdEncoding.EncodeToString(f.Bytes())
}

func (f ID) IntBytes() [8]byte {
	var b [8]byte
	binary.BigEndian.PutUint64(b[:], uint64(f))
	return b
}

func (f ID) Time() int64 {
	return (int64(f) >> timestampShift) + Epoch
}

func (f ID) WorkerId() int64 {
	return int64(f) & workerMask >> workerIdShift
}

func (f ID) Step() int64 {
	return int64(f) & stepMask
}

func (f ID) MarshalJSON() ([]byte, error) {
	buff := make([]byte, 0, 22)
	buff = append(buff, '"')
	buff = strconv.AppendInt(buff, int64(f), 10)
	buff = append(buff, '"')
	return buff, nil
}

func (f ID) UnmarshalJSON(b []byte) error {
	if len(b) < 3 || b[0] != '"' || b[len(b)-1] != '"' {
		return JsonError{b}
	}
	i, err := strconv.ParseInt(string(b[1:len(b)-1]), 10, 64)
	if err != nil {
		return err
	}
	f = ID(i)
	return nil
}

// ParseString 解析字符串
// 返回ID
func ParseString(id string) (ID, error) {
	i, err := strconv.ParseInt(id, 10, 64)
	return ID(i), err
}

// ParseInt64 解析int64
// 返回ID
func ParseInt64(id int64) ID {
	return ID(id)
}

// ParseBase2 解析二进制字符串
// 返回ID
func ParseBase2(id string) (ID, error) {
	i, err := strconv.ParseInt(id, 2, 64)
	return ID(i), err
}

// ParseBase32 解析base32字符串
// 返回ID
func ParseBase32(b []byte) (ID, error) {
	var id int64
	for i := range b {
		if decodeBase32Map[b[i]] == 0xFF {
			return -1, InvalidBase32Error
		}
		id = id*32 + int64(decodeBase32Map[b[i]])
	}
	return ID(id), nil
}

// ParseBase36 解析base36字符串
// 返回ID
func ParseBase36(id string) (ID, error) {
	i, err := strconv.ParseInt(id, 36, 64)
	return ID(i), err
}

// ParseBase58 解析base58字符串
// 返回ID
func ParseBase58(b []byte) (ID, error) {
	var id int64
	for i := range b {
		if decodeBase58Map[b[i]] == 0xFF {
			return -1, InvalidBase58Error
		}
		id = id*58 + int64(decodeBase58Map[b[i]])
	}
	return ID(id), nil
}

// ParseBytes 解析字节数组
// 返回ID
func ParseBytes(id []byte) (ID, error) {
	i, err := strconv.ParseInt(string(id), 10, 64)
	return ID(i), err
}

// ParseIntBytes 解析8字节数组
// 返回ID
func ParseIntBytes(id [8]byte) ID {
	return ID(int64(binary.BigEndian.Uint64(id[:])))
}
